﻿using ApiTemplateGenerate.Extend;
using ApiTemplateGenerate.ModelInfo;
using ApiTemplateGenerate.TypeConvert;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using RazorEngine.Templating;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

namespace ApiTemplateGenerate
{
    public class ApiToTsMiddleware
    {
        private static bool isStart = true;
        private readonly RequestDelegate _next;
        private readonly IApiDescriptionGroupCollectionProvider _IApiDescriptionGroupCollectionProvider;
        private readonly FileInfo _fileInfo;
        public ApiToTsMiddleware(IApiDescriptionGroupCollectionProvider apiDescriptionGroupCollectionProvider, RequestDelegate next, FileInfo fileInfo)
        {
            _IApiDescriptionGroupCollectionProvider = apiDescriptionGroupCollectionProvider;
            _next = next;
            _fileInfo = fileInfo;
        }
        public async Task Invoke(HttpContext httpContext)
        {
            try
            {
                if (isStart)
                {
                    List<ApiInfo> apiInfos = new List<ApiInfo>();
                    var groups = _IApiDescriptionGroupCollectionProvider.ApiDescriptionGroups.Items;
                    foreach (var group in groups)
                    {
                        foreach (var item2 in group.Items)
                        {
                            ApiInfo apiInfo = new ApiInfo();
                            //apiInfo.Describe
                            apiInfo.ApiUrl = item2.RelativePath;
                            apiInfo.ControllerName = ((Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor)item2.ActionDescriptor).ControllerName;
                            apiInfo.ActionName = ((Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor)item2.ActionDescriptor).ActionName;
                            apiInfo.RequestMethod = item2.HttpMethod;
                            apiInfo.Describe = ((Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor)item2.ActionDescriptor).MethodInfo.GetDescribe();
                            List<ParameterInfo> parameterInfos = new List<ParameterInfo>();
                            foreach (var parameterDescription in item2.ParameterDescriptions)
                            {
                                //parameterDescription.Type
                                parameterInfos.Add(new ParameterInfo
                                {
                                    DefaultValue = parameterDescription.DefaultValue,
                                    IsRequired = parameterDescription.IsRequired,
                                    Name = parameterDescription.Name,
                                    ParameteType = parameterDescription.Type,
                                    SourceType = DicCommon.SourceTypeDic[parameterDescription.Source.DisplayName]
                                });
                            }
                            apiInfo.ParameterInfos = parameterInfos;
                            ReturnInfo returnInfo = new ReturnInfo();
                            if (item2.SupportedResponseTypes.Count > 0)
                            {
                                var returnRrsult = item2.SupportedResponseTypes[0];
                                returnInfo.ReturnType = returnRrsult.Type;
                                returnInfo.ReturnValue = AnalysisType.GetMethodReturn(returnRrsult.Type);
                                apiInfo.ReturnInfo = returnInfo;
                            }
                            apiInfos.Add(apiInfo);
                        }
                    }

                    List<ApiDescribe> apiDescribes = new List<ApiDescribe>();

                    #region 注释
                    //foreach (var apiInfo in apiInfos)
                    //{
                    //    ApiDescribe apiDescribe = new ApiDescribe();
                    //    apiDescribe.ApiUrl = apiInfo.ApiUrl;
                    //    apiDescribe.Describe = apiInfo.Describe;
                    //    apiDescribe.MethodName = apiInfo.ActionName;
                    //    apiDescribe.RequestMethod = apiInfo.RequestMethod;
                    //    apiDescribe.ReturnValue = apiInfo.ReturnInfo.ReturnValue;
                    //    apiDescribe.IsNullParam = apiInfo.ParameterInfos.Count == 0;
                    //    StringBuilder ParameterString = new StringBuilder();
                    //    StringBuilder Params = new StringBuilder();
                    //    Params.Append("{");
                    //    bool isPath = false;
                    //    foreach (var item in apiInfo.ParameterInfos)
                    //    {
                    //        ParameterString.Append($"{item.Name},");
                    //        switch (item.SourceType)
                    //        {
                    //            case SourceType.Query:
                    //                apiDescribe.IsParams = true;
                    //                Params.Append($"{item}:{item},");
                    //                break;
                    //            case SourceType.Path:
                    //                if (!isPath) { apiDescribe.ApiUrl = apiDescribe.ApiUrl.Replace("{", "${");isPath = true; };

                    //                break;
                    //            case SourceType.Body:
                    //                apiDescribe.IsData = true;
                    //                apiDescribe.Data = item.Name;
                    //                break;
                    //            default:
                    //                break;
                    //        }
                    //    }
                    //    if (ParameterString.Length > 0)
                    //    {
                    //        ParameterString.Remove(ParameterString.Length - 1, 1);
                    //    }
                    //    if (Params.Length > 1)
                    //    {
                    //        Params.Remove(Params.Length - 1, 1);
                    //        Params.Append("}");
                    //    }
                    //    apiDescribe.Params = Params.ToString();
                    //    apiDescribe.ParameterString = ParameterString.ToString();
                    //    apiDescribes.Add(apiDescribe);
                    //}
                    #endregion

                    //Console.WriteLine(resultString);

                    var fileName = apiInfos.GroupBy(e => e.ControllerName).ToList();
                    var content = new StringBuilder();
                    foreach (var item in fileName)
                    {
                        content.Clear();
                        apiDescribes = new List<ApiDescribe>();
                        TsModelDescribes = new List<TsModelDescribe>();
                        TsEnumDescribes = new List<TsEnumDescribe>();
                        foreach (var apiInfo in apiInfos.Where(e => e.ControllerName == item.Key))
                        {
                            //content.Clear();
                            ApiDescribe apiDescribe = new ApiDescribe
                            {
                                ApiUrl = apiInfo.ApiUrl,
                                Describe = apiInfo.Describe,
                                MethodName = apiInfo.ActionName,
                                RequestMethod = apiInfo.RequestMethod,
                                ReturnValue = apiInfo.ReturnInfo?.ReturnValue,
                                IsNullParam = apiInfo.ParameterInfos.Count == 0
                            };
                            StringBuilder ParameterString = new StringBuilder();
                            StringBuilder Params = new StringBuilder();
                            Params.Append("{");
                            var describes = new StringBuilder();
                            describes.Append("/**;");
                            describes.Append($"* {apiInfo.Describe};");
                            bool isPath = false;
                            foreach (var parameterInfo in apiInfo.ParameterInfos)
                            {
                                var result = TsAnalysisType(parameterInfo.ParameteType.GetTypeInfo());
                                describes.Append($"* @param {parameterInfo.Name} {result.Type};");
                                ParameterString.Append($"{parameterInfo.Name}:{result.Type},");
                                switch (parameterInfo.SourceType)
                                {
                                    case SourceType.Query:
                                        apiDescribe.IsParams = true;
                                        Params.Append($"{parameterInfo.Name}:{parameterInfo.Name},");
                                        break;
                                    case SourceType.Path:
                                        if (!isPath) { apiDescribe.ApiUrl = apiDescribe.ApiUrl.Replace("{", "${"); isPath = true; };

                                        break;
                                    case SourceType.Body:
                                        apiDescribe.IsData = true;
                                        apiDescribe.Data = parameterInfo.Name;
                                        break;
                                    default:
                                        break;
                                }
                            }

                            if (ParameterString.Length > 0)
                            {
                                ParameterString.Remove(ParameterString.Length - 1, 1);
                            }
                            if (Params.Length > 1)
                            {
                                Params.Remove(Params.Length - 1, 1);
                                Params.Append("}");
                            }
                            apiDescribe.Params = Params.ToString();
                            apiDescribe.ParameterString = ParameterString.ToString();


                            #region 分析类型
                            TypeExtend.ClassNames = new HashSet<string>();
                            if (apiInfo.ReturnInfo != null)
                            {
                                var rssult = TsAnalysisType(apiInfo.ReturnInfo.ReturnType.GetTypeInfo());
                                describes.Append($"* @returns {rssult.Type};");
                                describes.Append($"*/");
                                //TypeExtend.ClassNames = new HashSet<string>();
                            }

                            foreach (var item3 in apiInfo.ParameterInfos)
                            {
                                //TypeExtend.ClassNames = new HashSet<string>();
                                TsAnalysisType(item3.ParameteType.GetTypeInfo());
                                TypeExtend.ClassNames = new HashSet<string>();
                            }
                            apiDescribe.Describes = describes.ToString();
                            apiDescribes.Add(apiDescribe);
                            #endregion
                        }
                        var resultString1 = RazorEngine.Engine.Razor.RunCompile(ApiTemplateGenerate.TemplateTsAxios.TemplateMethod.Template, "templateKeyMethod", null, apiDescribes);
                        resultString1 = resultString1.Replace("&lt;", "<");
                        resultString1 = resultString1.Replace("&gt;", ">");
                        content.Append(resultString1);
                        //接口生成
                        foreach (var interfaceDescribe in TsModelDescribes.DistinctEx(e => new { e.ClassName }))
                        {
                            var resultString = RazorEngine.Engine.Razor.RunCompile(ApiTemplateGenerate.TemplateTsAxios.TemplateInterface.Template, "templateKeyInterface", null, interfaceDescribe);
                            resultString = resultString.Replace("&lt;", "<");
                            resultString = resultString.Replace("&gt;", ">");
                            content.Append(resultString);
                        }
                        //枚举生成
                        TsEnumDescribes = TsEnumDescribes.DistinctEx(e => new { e.Name }).ToList();

                        var resultString2 = RazorEngine.Engine.Razor.RunCompile(ApiTemplateGenerate.TemplateTsAxios.TemplateEnum.Template, "templateKeyEnum", null, TsEnumDescribes);
                        resultString2 = resultString2.Replace("&lt;", "<");
                        resultString2 = resultString2.Replace("&gt;", ">");
                        content.Append(resultString2);


                        GenerateFile($"{_fileInfo.DirectoryPath}/{_fileInfo.FolderName}/", $"/{item.Key}.ts", content.ToString());
                    }
                    await _next(httpContext);
                    isStart = false;
                }
                else
                {
                    await _next(httpContext);
                }
            }
            catch (Exception ex)
            {
                httpContext.Response.ContentType = "application/problem+json";

                var title = "An error occured: " + ex.Message;
                var details = ex.ToString();

                var problem = new
                {
                    Status = 500,
                    Title = title,
                    Detail = details
                };

                //Serialize the problem details object to the Response as JSON (using System.Text.Json)
                var stream = httpContext.Response.Body;
                await JsonSerializer.SerializeAsync(stream, problem);
            }
        }
        /// <summary>
        /// 写入磁盘
        /// </summary>
        /// <param name="directoryPath">目录</param>
        /// <param name="path">文件</param>
        /// <param name="content">内容</param>
        private static void GenerateFile(string directoryPath, string path, string content)
        {

            if (File.Exists(directoryPath + path))
            {
                File.Delete(directoryPath + path);
            }
            if (!Directory.Exists(directoryPath))
            {
                Directory.CreateDirectory(directoryPath);
            }


            //创建文本文件Demo.txt，并写入初始字符串"123456789"
            using (var fileStream = new FileStream(directoryPath + path, FileMode.CreateNew))
            {
                var result = content.Replace(";", "\r\n");
                byte[] data = System.Text.Encoding.UTF8.GetBytes(result);//
                fileStream.Write(data, 0, data.Length);
            }
        }
        /// <summary>
        /// 接口
        /// </summary>
        private static List<TsModelDescribe> TsModelDescribes { get; set; } = new List<TsModelDescribe>();
        /// <summary>
        /// 枚举
        /// </summary>
        private static List<TsEnumDescribe> TsEnumDescribes { get; set; } = new List<TsEnumDescribe>();
        /// <summary>
        /// 填充类和接口
        /// </summary>
        /// <param name="typeInfo"></param>
        /// <returns></returns>
        private Api TsAnalysisType(ApiTemplateGenerate.ModelInfo.TypeInfo typeInfo)
        {
            //Console.WriteLine(typeInfo.Name);
            switch (typeInfo.TypeEnum)
            {
                case ApiTemplateGenerate.ModelInfo.TypeEnum.Class:
                    {
                        var result = new TsModelDescribe();
                        result.ClassDescribe = typeInfo.Describe;
                        result.ClassName = typeInfo.Type.Name;

                        foreach (var item in typeInfo.TypeInfoChild)
                        {
                            var tsModelPropertyDescribe = new TsModelPropertyDescribe();
                            var resultChild = TsAnalysisType(item);
                            tsModelPropertyDescribe.IsNull = resultChild.IsNull;
                            tsModelPropertyDescribe.PropertyDescribe = resultChild.Describe;
                            tsModelPropertyDescribe.PropertyName = resultChild.Name.FirstCharToLower();
                            tsModelPropertyDescribe.PropertyType = resultChild.Type;
                            result.ModelPropertyDescribes.Add(tsModelPropertyDescribe);
                        }
                        TsModelDescribes.Add(result);
                        return new Api { Describe = typeInfo.Describe, IsNull = false, Name = typeInfo.Name, Type = typeInfo.Type.Name, Vlaue = typeInfo.Value, TypeEnum = ModelInfo.TypeEnum.Class };
                    }
                case ApiTemplateGenerate.ModelInfo.TypeEnum.BaseType:
                    return new Api { Describe = typeInfo.Describe, IsNull = false, Name = typeInfo.Name, Type = TsType.TsTypeDic[typeInfo.Type.Name], Vlaue = typeInfo.Value, TypeEnum = ModelInfo.TypeEnum.BaseType };
                case ApiTemplateGenerate.ModelInfo.TypeEnum.IEnumerable:
                    {
                        var result = new TsModelDescribe();
                        result.ClassDescribe = typeInfo.Describe;
                        result.ClassName = typeInfo.Name;
                        var type = "any";
                        var isStart = true;
                        foreach (var item in typeInfo.TypeInfoChild)
                        {
                            var resultChild = TsAnalysisType(item);
                            if (isStart)
                            {
                                type = resultChild.Type;
                            }
                            isStart = false;
                        }
                        //TsModelDescribes.Add(result);
                        return new Api { Describe = typeInfo.Describe, IsNull = false, Name = typeInfo.Name, Type = $"Array<{type}>", Vlaue = typeInfo.Value, TypeEnum = ModelInfo.TypeEnum.IEnumerable };
                    }
                case ApiTemplateGenerate.ModelInfo.TypeEnum.IsArray:
                    return new Api { Describe = typeInfo.Describe, IsNull = false, Name = typeInfo.Name, Type = $"Array<{TsType.TsTypeDic[typeInfo.Type.Name.Replace("[]", string.Empty)]}>", Vlaue = typeInfo.Value, TypeEnum = ModelInfo.TypeEnum.IsArray };
                case ApiTemplateGenerate.ModelInfo.TypeEnum.Generic:
                    {
                        var result = new TsModelDescribe();
                        result.ClassDescribe = typeInfo.Describe;
                        if (typeInfo.Type.Name.ToString().ToLower().Contains("dictionary"))//字典类型了
                        {
                            var key = "";
                            var value = "";

                            var resultChild = TsAnalysisType(typeInfo.Type.GenericTypeArguments[0].GetTypeInfo());
                            key = resultChild.Type;
                            var resultChild2 = TsAnalysisType(typeInfo.Type.GenericTypeArguments[1].GetTypeInfo());
                            value = resultChild2.Type;
                            string dicTs=string.Format("[key:{0}]:{1}",key,value);
                            return new Api { Describe = typeInfo.Describe, IsNull = false, Name = typeInfo.Name, Type = "{" + dicTs + "}", Vlaue = typeInfo.Value, TypeEnum = ModelInfo.TypeEnum.Generic };
                        }
                        result.ClassName = $"{typeInfo.Type.Name.Replace("`1", string.Empty)}<T>";
                        var type = "any";
                        foreach (var item in typeInfo.TypeInfoChild)
                        {

                            var tsModelPropertyDescribe = new TsModelPropertyDescribe();
                            var resultChild = TsAnalysisType(item);
                            tsModelPropertyDescribe.IsNull = resultChild.IsNull;
                            tsModelPropertyDescribe.PropertyDescribe = resultChild.Describe;
                            tsModelPropertyDescribe.PropertyName = resultChild.Name.FirstCharToLower();
                            tsModelPropertyDescribe.PropertyType = resultChild.Type;
                            if (item.IsT)
                            {
                                type = item.Type.Name;
                                if (resultChild.TypeEnum == ModelInfo.TypeEnum.IEnumerable)
                                {
                                    tsModelPropertyDescribe.PropertyType = "Array<T>";
                                    type = resultChild.Type.Replace("Array<", "").Replace(">", "");
                                }
                                //else if (resultChild.TypeEnum == ModelInfo.TypeEnum.Generic)
                                //{
                                //    tsModelPropertyDescribe.PropertyType = T;
                                //}
                                else
                                {
                                    tsModelPropertyDescribe.PropertyType = item.IsT ? "T" : resultChild.Type;
                                }

                            }
                            result.ModelPropertyDescribes.Add(tsModelPropertyDescribe);
                        }
                        TsModelDescribes.Add(result);
                        return new Api { Describe = typeInfo.Describe, IsNull = false, Name = typeInfo.Name, Type = $"{typeInfo.Type.Name.Replace("`1", string.Empty)}<{type}>", Vlaue = typeInfo.Value, TypeEnum = ModelInfo.TypeEnum.Generic };
                    }
                case ApiTemplateGenerate.ModelInfo.TypeEnum.Enum:
                    {
                        var result = new TsEnumDescribe();
                        result.Describe = typeInfo.Describe;
                        result.Name = typeInfo.Type.Name;
                        List<TsEnumFiledDescribe> tsEnumFiledDescribe = new List<TsEnumFiledDescribe>();
                        foreach (var item in typeInfo.TypeInfoChild)
                        {
                            tsEnumFiledDescribe.Add(new TsEnumFiledDescribe
                            {
                                Describe = item.Describe,
                                EnumName = item.Name,
                                Value = item.Value
                            });
                        }
                        result.TsEnumFiled = tsEnumFiledDescribe;
                        TsEnumDescribes.Add(result);
                    }
                    return new Api { Describe = typeInfo.Describe, IsNull = false, Name = typeInfo.Type.Name, Type = typeInfo.Name, Vlaue = typeInfo.Value, TypeEnum = ModelInfo.TypeEnum.Enum };
                case ApiTemplateGenerate.ModelInfo.TypeEnum.IsNullType:
                    var typeName = string.Empty;
                    //if (typeInfo.TypeInfoChild.Count > 0)
                    //{
                    //typeName = TsType.TsTypeDic[typeInfo.TypeInfoChild[0].Type.Name];
                    foreach (var item in typeInfo.TypeInfoChild)
                    {
                        var tsModelPropertyDescribe = new TsModelPropertyDescribe();
                        var resultChild = TsAnalysisType(item);
                        tsModelPropertyDescribe.IsNull = resultChild.IsNull;
                        tsModelPropertyDescribe.PropertyDescribe = resultChild.Describe;
                        tsModelPropertyDescribe.PropertyName = resultChild.Name;
                        tsModelPropertyDescribe.PropertyType = resultChild.Type;
                        typeName = resultChild.Type;
                    }
                    return new Api { Describe = typeInfo.Describe, IsNull = true, Name = typeInfo.Name, Type = typeName, Vlaue = typeInfo.Value, TypeEnum = ModelInfo.TypeEnum.IsNullType };
                //}
                //return new Api { Describe = typeInfo.Describe, IsNull = true, Name = typeInfo.Name, Type = typeName, Vlaue = typeInfo.Value, TypeEnum = ModelInfo.TypeEnum.IsNullType };
                case ApiTemplateGenerate.ModelInfo.TypeEnum.Other:
                    return new Api { Describe = typeInfo.Describe, IsNull = false, Name = typeInfo.Name, Type = typeInfo.Name, Vlaue = typeInfo.Value, TypeEnum = ModelInfo.TypeEnum.Other };
                default:
                    return new Api { Describe = typeInfo.Describe, IsNull = false, Name = typeInfo.Name, Type = typeInfo.Name, Vlaue = typeInfo.Value, TypeEnum = ModelInfo.TypeEnum.Other };
            }
        }

    }
}
